package compiler;

import java.util.ArrayList;
import static java.util.Collections.EMPTY_MAP;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import language.LArray;
import language.LArrayBuilder;
import language.LFunction;
import language.LSupplier;
import language.LType;

public class LocFunction extends LType implements Func {

    private final List list;
    private final int startIndex;
    final Map<String, LType> constants;
    private final char variable;

    public LocFunction(List list, Map<String, LType> constants) {
        this.list = new ArrayList(list);
        this.constants = constants;
        startIndex = 1;
        variable = (Character) list.get(1);
    }

    List list() {
        return list;
    }

    LType call(LType input, Map<Character, LType> vars) {
        //    System.out.println(new Instance(input, vars).next());
        return new Instance(input, vars).next();
    }

    @Override
    public LType accept(LType l, Map<Character, LType> vars) {
        return call(l, vars);
    }

    char variable() {
        return variable;
    }

    class Instance {

        private final LType input;
        private int index = startIndex;
        private final Map<Character, LType> vars;

        private Instance(LType input, Map<Character, LType> vars) {
            this.vars = vars;
            this.input = input;
        }

        LType next() {
            Object obj;
            do {
                index++;
                if (index > list.size()) {
                    return null;
                }
                obj = list.get(index);
            } while (obj.equals(' '));
            if (obj instanceof Character) {
                if (obj.equals('`')) {
                    return null;
                }
                if (obj.equals(']')) {
                    return null;
                    //            return LArray.of(new Scanner(System.in).nextLine());
                }
                obj = of(obj.toString());
            } else if (obj instanceof String) {
                obj = of((String) obj);
            } else if (obj instanceof If) {
                if (next().toBoolean()) {
                    LType next = next();
                    index = ((If) obj).els;
                    obj = next;
                } else {
                    index = ((If) obj).then;
                    LType next = next();
                    index = ((If) obj).els;
                    obj = next;
                }
            } else {
                obj = (LType) obj;
            }
            while (obj instanceof Sup || obj instanceof Func) {
                if (obj instanceof LSupplier) {
                    obj = ((LSupplier) obj).get(EMPTY_MAP);
                } else if (obj instanceof NamedSupplier) {
                    obj = ((NamedSupplier) obj).call();
                    //        } else if (obj instanceof LocSupplier) {
                    //xx            obj = ((LocSupplier) obj).get(EMPTY_MAP);
                } else if (obj instanceof LFunction) {
                    LType next = next();
                    if (next == null) {
                        if (obj instanceof LArrayBuilder) {
                            return ((LArrayBuilder) obj).asArray();
                        }
                        return (LType) obj;
                    }
                    obj = ((LFunction) obj).accept(next, new HashMap());
                } else if (obj instanceof NamedFunction) {
                    LType next = next();
                    if (next == null) {
                        if (obj instanceof LArrayBuilder) {
                            return ((LArrayBuilder) obj).asArray();
                        }
                        return (LType) obj;
                    }
                    obj = ((NamedFunction) obj).call(next);
                } else if (obj instanceof LocFunction) {
                    LType next = next();
                    if (next == null) {
                        if (obj instanceof LArrayBuilder) {
                            return ((LArrayBuilder) obj).asArray();
                        }
                        return ((LocFunction) obj).setVariable(variable, input);
                    }
                    Map<Character, LType> newVars = vars;
                    obj = ((LocFunction) obj).setVariable(variable, input).accept(next, newVars);
                }
            }
            return (LType) obj;
        }

        Object of(String s) {
            if (s.charAt(0) == variable) {
                return input;
            }
            if (vars.containsKey(s.charAt(0))) {
                return vars.get(s.charAt(0));
            }
            if (constants.containsKey(s)) {
                return constants.get(s);
            }
            throw new RuntimeException("Unrecongnized symbol: " + s);
        }
    }

    LocFunction setVariable(char var, LType entity) {
        List l = new ArrayList(list);
        for (int i = 2; i < l.size(); i++) {
            if (l.get(i).equals(var)) {
                l.set(i, entity);
            } else if (l.get(i) instanceof LocFunction) {
                l.set(i, ((LocFunction) l.get(i)).setVariable(var, entity));
            }
        }
        return new LocFunction(l, constants);
    }

    @Override
    public String toString() {
        return accept(LArray.of(new Scanner(System.in).nextLine()), new HashMap()).toString();
    }
}
